/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.filesystems;
import java.io.*;
import java.util.*;
import org.openide.util.enum.*;
/** The base for all filesystems that are build above a top of
* other ones. This system expects at most one filesystem it should write
* to and any number of filesystems to read from.
*
* If there is more versions of one file than the one from writable file system
* is prefered or the read only systems are scanned in the given order.
*
* @author Jaroslav Tulach
*/
public class MultiFileSystem extends FileSystem {
static final long serialVersionUID =-767493828111559560L;
/** what extension to add to file that mask another ones */
static final String MASK = "_hidden"; // NOI18N
/** array of fs. the file system at position 0 can be null, because
* it is writable file system. Others are only for read access
*/
final FileSystem[] systems;
/** root */
private transient MultiFileObject root;
/** index of the file system with write access */
private static final int WRITE_SYSTEM_INDEX = 0;
/** Creates new MultiFileSystem.
* @param array of file systems (can contain nulls)
*/
public MultiFileSystem (FileSystem[] fileSystems) {
this.systems = fileSystems;
}
/** This file system is readonly if it has not writable system.
*/
public boolean isReadOnly () {
return systems[WRITE_SYSTEM_INDEX] == null;
}
/** The name of the file system.
*/
public String getDisplayName () {
return getString ("CTL_MultiFileSystem");
}
/** Root of the file system.
*/
public FileObject getRoot () {
return getMultiRoot ();
}
/** Root of the file system.
*/
private MultiFileObject getMultiRoot () {
if (root == null) {
synchronized (this) {
if (root == null) {
root = new MultiFileObject (this);
}
}
}
return root;
}
/** No special actions.
*/
public org.openide.util.actions.SystemAction[] getActions () {
return new org.openide.util.actions.SystemAction[0];
}
/* Finds file when its name is provided.
*
* @param aPackage package name where each package is separated by a dot
* @param name name of the file (without dots) or <CODE>null</CODE> if
* one want to obtain name of package and not file in it
* @param ext extension of the file or <CODE>null</CODE> if one needs
* package and not file name
*
* @warning when one of name or ext is <CODE>null</CODE> then name and
* ext should be ignored and scan should look only for a package
*
* @return FileObject that represents file with given name or
* <CODE>null</CODE> if the file does not exist
*/
public FileObject find (String aPackage, String name, String ext) {
// create enumeration of name to look for
StringTokenizer st = new StringTokenizer (aPackage, "."); // NOI18N
Enumeration en;
if (name == null || ext == null) {
en = st;
} else {
en = new SequenceEnumeration (
st,
new SingletonEnumeration (name + '.' + ext)
);
}
// tries to find it (can return null)
return getMultiRoot ().find (en);
}
/* Finds file when its resource name is given.
* The name has the usual format for the {@link ClassLoader#getResource(String)}
* method. So it may consist of "package1/package2/filename.ext".
* If there is no package, it may consist only of "filename.ext".
*
* @param name resource name
*
* @return FileObject that represents file with given name or
* <CODE>null</CODE> if the file does not exist
*/
public FileObject findResource (String name) {
if (name.length () == 0) {
return getMultiRoot ();
} else {
StringTokenizer tok = new StringTokenizer (name, "/"); // NOI18N
return getMultiRoot ().find (tok);
}
}
//
// Helper methods for subclasses
//
/** For given file object finds the file system that the object is placed on.
* The object must be created by this file system orherwise IllegalArgumentException
* is thrown.
*
* @param fo file object
* @return the file system (from the list we delegate to) the object has file on
* @exception IllegalArgumentException if the file object is not represented in this file system
*/
protected final FileSystem findSystem (FileObject fo) throws IllegalArgumentException {
try {
if (fo instanceof MultiFileObject) {
MultiFileObject mfo = (MultiFileObject)fo;
return mfo.getLeaderFileSystem ();
}
} catch (FileStateInvalidException ex) {
}
throw new IllegalArgumentException (fo.toString());
}
/** Marks a resource as hidden. It will not be listed in the list of files.
* Uses createMaskOn method to determine on which file system to mark the file.
*
* @param res resource name of file to hide or show
* @param hide true if we should hide the file/false otherwise
* @exception IOException if it is not possible
*/
protected final void hideResource (String res, boolean hide) throws IOException {
if (hide) {
// mask file
maskFile (createWritableOn (res), res);
} else {
unmaskFile (createWritableOn (res), res);
}
}
/** Finds all hidden files on given file system. The methods scans all files for
* ones with hidden extension and returns enumeration of names of files
* that are hidden.
*
* @param folder folder to start at
* @param rec proceed recursivelly
* @return enumeration of String with names of hidden files
*/
protected static Enumeration hiddenFiles (FileObject folder, boolean rec) {
Enumeration allFiles = folder.getChildren (rec);
Enumeration allNull = new AlterEnumeration (allFiles) {
public Object alter (Object fo) {
String sf = ((FileObject)fo).getPackageNameExt ('/', '.');
if (sf.endsWith (MASK)) {
return sf.substring (0, sf.length () - MASK.length ());
} else {
return null;
}
}
};
return new FilterEnumeration (allNull) {
public boolean accept (Object o) {
return o != null;
}
};
}
//
// methods for subclass customization
//
/** Finds the system to create writable version of the file on.
*
* @param name name of the file (full)
* @return the first one
* @exception IOException if the file system is readonly
*/
protected FileSystem createWritableOn (String name) throws IOException {
if (systems[WRITE_SYSTEM_INDEX] == null) {
FSException.io ("EXC_FSisRO", getDisplayName ()); // NOI18N
}
return systems[WRITE_SYSTEM_INDEX];
}
/** Notification that a file has migrated from one file system
* to another. Usually when somebody writes to file on readonly file
* system and the file has to be copied to write one.
* <P>
* This method allows subclasses to fire for example FileSystem.PROP_STATUS
* change to notify that annotation of this file should change.
*
* @param fo file object that change its actual file system
*/
protected void notifyMigration (FileObject fo) {
}
/** Notification that a file has been marked unimportant.
*
*
* @param fo file object that change its actual file system
*/
protected void markUnimportant (FileObject fo) {
}
//
// Private methods
//
/** Receives name of a resource and array of three elements and
* splits the name into folder, name and extension.
*
* @param res resource name
* @param store array to store data to
*/
private static String[] split (String res, String[] store) {
if (store == null) {
store = new String[3];
}
int file = res.lastIndexOf ('/');
int dot = res.lastIndexOf ('.');
if (file == -1) {
store[0] = ""; // NOI18N
} else {
store[0] = res.substring (0, file);
}
file++;
if (dot == -1) {
store[1] = res.substring (file);
store[2] = ""; // NOI18N
} else {
store[1] = res.substring (file, dot);
store[2] = res.substring (dot + 1);
}
return store;
}
/** Computes a list of FileObjects in the right order
* that can represent this instance.
*
* @param name of resource to find
* @return enumeration of FileObject
*/
Enumeration delegates (final String name) {
Enumeration en = new ArrayEnumeration (systems);
Enumeration objsAndNulls = new AlterEnumeration (en) {
public Object alter (Object o) {
FileSystem fs = (FileSystem)o;
if (fs == null) {
return null;
} else {
return fs.findResource(name);
}
}
};
return new FilterEnumeration (objsAndNulls) {
public boolean accept (Object o) {
return o != null;
}
};
}
/** Creates a file object that will mask the given file.
* @param fs file system to work on
* @param res resource name of the file
* @exception IOException if it fails
*/
static void maskFile (FileSystem fs, String res) throws IOException {
FileObject fo = FileUtil.createData (fs.getRoot (), res + MASK);
}
/** Creates a file object that will mask the given file.
* @param fs file system to work on
* @param res resource name of the file
* @exception IOException if it fails
*/
static void unmaskFile (FileSystem fs, String res) throws IOException {
FileObject fo = fs.findResource (res + MASK);
if (fo != null) {
FileLock lock = fo.lock ();
try {
fo.delete (lock);
} finally {
lock.releaseLock ();
}
}
}
}
/*
* Log
* 11 Gandalf 1.10 1/12/00 Ian Formanek NOI18N
* 10 Gandalf 1.9 12/30/99 Jaroslav Tulach New dialog for
* notification of exceptions.
* 9 Gandalf 1.8 11/3/99 Jaroslav Tulach Can create new files over
* hidden ones.
* 8 Gandalf 1.7 10/29/99 Jaroslav Tulach MultiFileSystem +
* FileStatusEvent
* 7 Gandalf 1.6 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 6 Gandalf 1.5 8/9/99 Ian Formanek Generated Serial Version
* UID
* 5 Gandalf 1.4 8/9/99 Ian Formanek Generated Serial Version
* UID
* 4 Gandalf 1.3 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 3 Gandalf 1.2 6/1/99 Jaroslav Tulach delete works.
* 2 Gandalf 1.1 5/31/99 Jaroslav Tulach Write/rename/delete
* 1 Gandalf 1.0 5/20/99 Jaroslav Tulach
* $
*/